/*
 * ChaudCast
 * Copyright (C) 2009 Stefan Dimitrov
 * http://www.chaudcast.com/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * See COPYING.TXT for details.
 */


package com.chaudcast;

import java.io.IOException;

import javax.microedition.media.Manager;
import javax.microedition.media.MediaException;
import javax.microedition.media.Player;

import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.BasicEditField;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.container.HorizontalFieldManager;
import net.rim.device.api.ui.container.MainScreen;

/**
 * Provides a GUI interface for buffered media playback from a remotely
 * streamed ShoutCast source.
 */
public final class ChaudCast extends UiApplication
{
	private static ChaudCast app;
	private static ChaudCastScreen screen;
    /**
     * Entry point for application
     * @param args Command line arguments (not used)
     */
    public static void main(String[] args)
    {
        // Create a new instance of the application and make the currently
        // running thread the application's event dispatching thread.
        app = new ChaudCast();
        app.enterEventDispatcher();
    }
    
    /**
     * Creates the main screen and pushes it onto the display stack
     */
    public ChaudCast()
    {
        screen = new ChaudCastScreen();
        pushScreen(screen);
    }
    
    /**
     * Presents a dialog to the user with a given message
     * @param message The text to display
     */
    public static void errorDialog(final String message)
    {
        UiApplication.getUiApplication().invokeLater(new Runnable()
        {
            public void run()
            {
                Dialog.alert(message);
            } 
        });
    }

    /**
     * The main screen of the application
     */
    private static final class ChaudCastScreen extends MainScreen implements FieldChangeListener
    {
        /** A field used to enter the URL of the remote media file */
        private BasicEditField _urlField;

        /** A field used to display the number of bytes that have been loaded */
        private BasicEditField _loadStatusField;

        /** A field used to display the current status of the media player */
        private BasicEditField _playStatusField;

        /**
         * A field which contains the minimum number of bytes that must be
         * buffered before the media file will begin playing.
         */
        private BasicEditField _startBufferField;

        /**
         * A field which contains the minimum forward byte buffer which 
         * must be maintained in order for the video to keep playing. If
         * the forward buffer falls below this number, the playback will
         * pause until the buffer increases.
         */
        private BasicEditField _pauseBytesField;

        /**
         * A field which contains the minimum forward byte buffer required
         * to resume playback after a pause.
         */
        private BasicEditField _resumeBytesField;

        /** A field which contains the maximum byte size of a single read */
        private BasicEditField _readLimitField;

        /** A button which starts the HTTP request and media playback */
        private ButtonField _startPlayingButton;

        /** A button which stops the HTTP request and media playback */
        private ButtonField _stopPlayingButton;

        /** A stream for the resource we are retrieving */
        private ShoutcastStream _source;

        /** A player for the media stream. */
        private Player _player;

        /** A thread which creates and starts the Player */
        private PlayerThread _playerThread;

        // Constructor
        private ChaudCastScreen()
        {
            // Set the title of the window
            setTitle("ChaudCast v.0.2");
            
            // Create and add the field for the URL to be retrieved
            _urlField = new BasicEditField("URL:","http://scfire-ntc-aa04.stream.aol.com:80/stream/1074");
            add(_urlField);

            // Create the START, STOP and RESET buttons
            _startPlayingButton = new ButtonField("Play", ButtonField.CONSUME_CLICK);
            _stopPlayingButton = new ButtonField("Stop", ButtonField.CONSUME_CLICK);
            _startPlayingButton.setChangeListener(this);
            _stopPlayingButton.setChangeListener(this);
            
            // Add the player control buttons to the screen
            HorizontalFieldManager buttonlist = new HorizontalFieldManager();
            buttonlist.add(_startPlayingButton);
            buttonlist.add(_stopPlayingButton);
            add(buttonlist);

            // Create and add the field with the load progress
            _loadStatusField = new BasicEditField("Load: ","0 Bytes", 10, Field.NON_FOCUSABLE);
            add(_loadStatusField);

            // Create and add the field with the player status
            _playStatusField = new BasicEditField("Play: ","Stopped", 10, Field.NON_FOCUSABLE);
            add(_playStatusField);

            // Create and add the field with the starting buffer
            _startBufferField = new BasicEditField("Starting Buffer: ", "128000", 10, BasicEditField.FILTER_INTEGER | Field.NON_FOCUSABLE);
            add(_startBufferField);

            // Create and add the field with the minimum pause buffer
            _pauseBytesField = new BasicEditField("Pause At: ","64000", 10, BasicEditField.FILTER_INTEGER | Field.NON_FOCUSABLE);
            add(_pauseBytesField);

            // Create and add the field with the minimum resume buffer
            _resumeBytesField = new BasicEditField("Resume At: ","128000", 10, BasicEditField.FILTER_INTEGER | Field.NON_FOCUSABLE);
            add(_resumeBytesField);

            // Create and add the field with the read limit
            _readLimitField = new BasicEditField("Read Limit: ","64000", 10, BasicEditField.FILTER_INTEGER | Field.NON_FOCUSABLE);
            add(_readLimitField);
        }

        /**
         * A common listener for all three player controls
         * @param field The field that changed
         * @param context Information specifying the origin of the change
         */
        public void fieldChanged(Field field, int context)
        {
            try
            {
                // If the START button was pressed, begin playback
                if (field == _startPlayingButton)
                {
                    // The player does not exist, we must initialize it
                    if (_player == null)
                    { 
                        // Create a stream using the remote file.
                        _source = new ShoutcastStream(_urlField.getText());
                        
                        // Set the attributes of the stream using the
                        // information from the GUI fields.
                        /*
                        _source.setStartBuffer(Integer.parseInt(_startBufferField.getText()));
                        _source.setReadLimit(Integer.parseInt(_readLimitField.getText()));
                        _source.setResumeBytes(Integer.parseInt(_resumeBytesField.getText()));
                        _source.setPauseBytes(Integer.parseInt(_pauseBytesField.getText()));
                        _source.setLoadStatus(_loadStatusField);
                        _source.setPlayStatus(_playStatusField);
                        */
                        // Acquire the UI lock
                        UiApplication.getUiApplication().invokeLater(new Runnable()
                        {
                            public void run()
                            {
                                // Update the player status
                                _playStatusField.setText("Started");
                            }
                        });
                        
                        // Create and run the player's thread
                        _playerThread = new PlayerThread();
                        if (_playerThread != null) {
                        	_playerThread.start();
                        } else {
                        	System.out.println ("PlayerThread is null.");
                        }
                    }
                    // The player already exists, simply resume it
                    else
                    {
                        _player.start();
                    }
                }
                //If the STOP button was pressed:
                else if (field == _stopPlayingButton)
                {
                    // Acquire the UI lock
                    UiApplication.getUiApplication().invokeLater(new Runnable()
                    {
                        public void run()
                        {
                            //Update the status fields
                            _loadStatusField.setText("0 Bytes");
                            _playStatusField.setText("Stopped");
                        }
                    });
                    // Destroy the Player and streams
                    destroy();
                }
            }
            catch (IOException ioe)
            {
            	ioe.printStackTrace();
                errorDialog(ioe.toString());
            } catch (MediaException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				System.out.println(e);
			}

        }

        /**
         * Called when the application exits, ensures that all attributes are
         * destroyed correctly.
         */
        public void close()
        {
            try
            {
                // Destroy the Player and streams
                destroy();
            }
            catch (Exception e)
            {
                errorDialog("destroy() threw " + e.toString());
            }
            finally
            {
                super.close();                
            }
        }
        
        /**
         * Prevent the save dialog from being displayed
         * @see net.rim.device.api.ui.container.MainScreen#onSavePrompt()
         */
        protected boolean onSavePrompt()
        {
            return true;
        }
        
        /**
         * Destroy the Player and streams
         * @throws Exception Thrown if the media player could not be closed or if the connection stream could not be closed
         */
        private void destroy()
        {
            try {
            	// Destroy the player.
            	if (_player != null)
            	{
            		_player.stop();
            		_player = null;
            	}

            	// Destroy the stream.
            	if (_source != null)
            	{
            		_source.stop();
            		_source.disconnect();
            		_source = null;
            	}
            } catch (Exception e) {
            	System.out.println("An error of type "+e+" has occured.");
            }
        }

        /**
         * A thread for the media player
         */
        private class PlayerThread extends Thread
        {
            /**
             * Create and start the player
             * @see java.lang.Runnable#run()
             */
            public void run()
            {
                try
                {
                    _player = Manager.createPlayer(_source);
                    if (_player != null) 
                    	{
                    	_player.start();
                    	}
                    else System.out.println("Failed to start player");
                }
                catch (final Exception e)
                {
                    // Acquire the UI lock
                    UiApplication.getUiApplication().invokeLater(new Runnable()
                    {
                        public void run()
                        {
                            _playStatusField.setText("Stopped");
                            try {
                            	if (_player!= null) _player.stop();
                            	destroy();
                            } catch (Exception eNew) {
                            	System.out.println("Error: " + eNew.getMessage());
                            }
                            //_player = null;
                            System.out.println("Error: " + e.getMessage());
                        }
                    });
                }
            }
        }
        private void updateTotal(int newValue)
        {
            _loadStatusField.setText(""+newValue+" KB");
        }
        private void updateStatus(String newStatus)
        {
            _playStatusField.setText(newStatus);
        }
    }
    /**
     * Updates the total kbytes loaded field
     * @param newValue The value to display
     */
    public static void updateTotal(final int newValue)
    {
        UiApplication.getUiApplication().invokeLater(new Runnable()
        {
            public void run()
            {
                app.screen.updateTotal(newValue);
            } 
        });
    }
    /**
     * Updates the current status
     * @param newStatus the status to display
     */
    public static void updateStatus(final String newStatus)
    {
        UiApplication.getUiApplication().invokeLater(new Runnable()
        {
            public void run()
            {
                app.screen.updateStatus(newStatus);
            } 
        });
    }
}